<?php

include_once('OAuth2.inc');

/**
 * OAuth2 Elgg Implementation.
 */
class ElggOAuth2DataStore extends OAuth2 {
    // Stuff that should get overridden by subclasses.
    //
    // I don't want to make these abstract, because then subclasses would have
    // to implement all of them, which is too much work.
    //
    // So they're just stubs. Override the ones you need.

    /**
     * Return supported grant types.
     *
     * You should override this function with something, or else your OAuth
     * provider won't support any grant types!
     *
     * @return
     *   A list as below. If you support all grant types, then you'd do:
     * @code
     * return array(
     *   OAUTH2_GRANT_TYPE_AUTH_CODE,
     *   OAUTH2_GRANT_TYPE_USER_CREDENTIALS,
     *   OAUTH2_GRANT_TYPE_ASSERTION,
     *   OAUTH2_GRANT_TYPE_REFRESH_TOKEN,
     *   OAUTH2_GRANT_TYPE_NONE,
     * );
     * @endcode
     *
     * @ingroup oauth2_section_4
     */
    protected function getSupportedGrantTypes() {
        return array(
            OAUTH2_GRANT_TYPE_AUTH_CODE,
            OAUTH2_GRANT_TYPE_REFRESH_TOKEN,
            OAUTH2_GRANT_TYPE_USER_CREDENTIALS
        );
    }

    /**
     * Return supported authorization response types.
     *
     * You should override this function with your supported response types.
     *
     * @return
     *   A list as below. If you support all authorization response types,
     *   then you'd do:
     * @code
     * return array(
     *   OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE,
     *   OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN,
     *   OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN,
     * );
     * @endcode
     *
     * @ingroup oauth2_section_3
     */
    protected function getSupportedAuthResponseTypes() {
        return array(
            OAUTH2_AUTH_RESPONSE_TYPE_AUTH_CODE,
            OAUTH2_AUTH_RESPONSE_TYPE_ACCESS_TOKEN,
            OAUTH2_AUTH_RESPONSE_TYPE_CODE_AND_TOKEN
        );
    }

    /**
     * Return supported scopes.
     *
     * If you want to support scope use, then have this function return a list
     * of all acceptable scopes (used to throw the invalid-scope error).
     *
     * @return
     *   A list as below, for example:
     * @code
     * return array(
     *   'my-friends',
     *   'photos',
     *   'whatever-else',
     * );
     * @endcode
     *
     * @ingroup oauth2_section_3
     */
    protected function getSupportedScopes() {
        return array();
    }

    /**
     * Check restricted authorization response types of corresponding Client
     * identifier.
     *
     * If you want to restrict clients to certain authorization response types,
     * override this function.
     *
     * @param $client_id
     *   Client identifier to be check with.
     * @param $response_type
     *   Authorization response type to be check with, would be one of the
     *   values contained in OAUTH2_AUTH_RESPONSE_TYPE_REGEXP.
     *
     * @return
     *   TRUE if the authorization response type is supported by this
     *   client identifier, and FALSE if it isn't.
     *
     * @ingroup oauth2_section_3
     */
    protected function checkRestrictedAuthResponseType($client_id, $response_type) {
        return TRUE;
    }

    /**
     * Check restricted grant types of corresponding client identifier.
     *
     * If you want to restrict clients to certain grant types, override this
     * function.
     *
     * @param $client_id
     *   Client identifier to be check with.
     * @param $grant_type
     *   Grant type to be check with, would be one of the values contained in
     *   OAUTH2_GRANT_TYPE_REGEXP.
     *
     * @return
     *   TRUE if the grant type is supported by this client identifier, and
     *   FALSE if it isn't.
     *
     * @ingroup oauth2_section_4
     */
    protected function checkRestrictedGrantType($client_id, $grant_type) {
        return TRUE;
    }

    // Functions that help grant access tokens for various grant types.

    /**
     * Grant access tokens for basic user credentials.
     *
     * Check the supplied username and password for validity.
     *
     * You can also use the $client_id param to do any checks required based
     * on a client, if you need that.
     *
     * Required for OAUTH2_GRANT_TYPE_USER_CREDENTIALS.
     *
     * @param $client_id
     *   Client identifier to be check with.
     * @param $username
     *   Username to be check with.
     * @param $password
     *   Password to be check with.
     *
     * @return
     *   TRUE if the username and password are valid, and FALSE if it isn't.
     *   Moreover, if the username and password are valid, and you want to
     *   verify the scope of a user's access, return an associative array
     *   with the scope values as below. We'll check the scope you provide
     *   against the requested scope before providing an access token:
     * @code
     * return array(
     *   'scope' => <stored scope values (space-separated string)>,
     * );
     * @endcode
     *
     * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.2
     *
     * @ingroup oauth2_section_4
     */
    protected function checkUserCredentials($client_id, $username, $password) {
        return oauth2_auth_password($username, $password);
    }

    /**
     * Grant access tokens for assertions.
     *
     * Check the supplied assertion for validity.
     *
     * You can also use the $client_id param to do any checks required based
     * on a client, if you need that.
     *
     * Required for OAUTH2_GRANT_TYPE_ASSERTION.
     *
     * @param $client_id
     *   Client identifier to be check with.
     * @param $assertion_type
     *   The format of the assertion as defined by the authorization server.
     * @param $assertion
     *   The assertion.
     *
     * @return
     *   TRUE if the assertion is valid, and FALSE if it isn't. Moreover, if
     *   the assertion is valid, and you want to verify the scope of an access
     *   request, return an associative array with the scope values as below.
     *   We'll check the scope you provide against the requested scope before
     *   providing an access token:
     * @code
     * return array(
     *   'scope' => <stored scope values (space-separated string)>,
     * );
     * @endcode
     *
     * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3
     *
     * @ingroup oauth2_section_4
     */
    protected function checkAssertion($client_id, $assertion_type, $assertion) {
        return FALSE;
    }

    /**
     * Grant refresh access tokens.
     *
     * Retrieve the stored data for the given refresh token.
     *
     * Required for OAUTH2_GRANT_TYPE_REFRESH_TOKEN.
     *
     * @param $refresh_token
     *   Refresh token to be check with.
     *
     * @return
     *   An associative array as below, and NULL if the refresh_token is
     *   invalid:
     *   - client_id: Stored client identifier.
     *   - expires: Stored expiration unix timestamp.
     *   - scope: (optional) Stored scope values in space-separated string.
     *
     * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.4
     *
     * @ingroup oauth2_section_4
     */
    protected function getRefreshToken($refresh_token) {
        $pairs = array();

        $pairs[] = array(
            'name' => 'refresh_token',
            'value' => $refresh_token
        );

        $tokenEnts =
                elgg_get_entities_from_metadata(
                        array(
                            'metadata_name_value_pairs' => $pairs,
                            'type' => 'object',
                            'subtype' => 'greengar_oauth2_refreshtoken'
                        )
        );

        if ($tokenEnts && $tokenEnts[0]) {
            $tokenEnt = $tokenEnts[0];
            $ces = array(
                'owner_guid' => $tokenEnt->owner_guid,
                'client_id' => $tokenEnt->client_id,
                'expires' => $tokenEnt->expires,
                'scope' => $tokenEnt->scope,
                'test_refresh_token' => $refresh_token
            );
            return $ces;
        }

        return NULL;
    }

    /**
     * Take the provided refresh token values and store them somewhere.
     *
     * This function should be the storage counterpart to getRefreshToken().
     *
     * If storage fails for some reason, we're not currently checking for
     * any sort of success/failure, so you should bail out of the script
     * and provide a descriptive fail message.
     *
     * Required for OAUTH2_GRANT_TYPE_REFRESH_TOKEN.
     *
     * @param $refresh_token
     *   Refresh token to be stored.
     * @param $client_id
     *   Client identifier to be stored.
     * @param $expires
     *   expires to be stored.
     * @param $scope
     *   (optional) Scopes to be stored in space-separated string.
     *
     * @ingroup oauth2_section_4
     */
    protected function setRefreshToken($refresh_token, $client_id, $expires, $scope = NULL) {

        $tokenEnt = new ElggObject();
        $tokenEnt->subtype = 'greengar_oauth2_refreshtoken';

        $tokenEnt->owner_guid = get_loggedin_userid();
        $tokenEnt->container_guid = get_loggedin_userid();

        $tokenEnt->refresh_token = $refresh_token;
        $tokenEnt->client_id = $client_id;
        $tokenEnt->expires = $expires;
        $tokenEnt->scope = $scope;

        $tokenEnt->access_id = ACCESS_PUBLIC;

        $tokenEnt->save();

        return $tokenEnt->getGUID();
    }

    /**
     * Expire a used refresh token.
     *
     * This is not explicitly required in the spec, but is almost implied.
     * After granting a new refresh token, the old one is no longer useful and
     * so should be forcibly expired in the data store so it can't be used again.
     *
     * If storage fails for some reason, we're not currently checking for
     * any sort of success/failure, so you should bail out of the script
     * and provide a descriptive fail message.
     *
     * @param $refresh_token
     *   Refresh token to be expirse.
     *
     * @ingroup oauth2_section_4
     */
    protected function unsetRefreshToken($refresh_token) {
        $token = $this->getRefreshToken($refresh_token);
        return $token->delete();
    }

    /**
     * Grant access tokens for the "none" grant type.
     *
     * Not really described in the IETF Draft, so I just left a method
     * stub... Do whatever you want!
     *
     * Required for OAUTH2_GRANT_TYPE_NONE.
     *
     * @ingroup oauth2_section_4
     */
    protected function checkNoneAccess($client_id) {
        return FALSE;
    }

    /**
     * Get default authentication realm for WWW-Authenticate header.
     *
     * Change this to whatever authentication realm you want to send in a
     * WWW-Authenticate header.
     *
     * @return
     *   A string that you want to send in a WWW-Authenticate header.
     *
     * @ingroup oauth2_error
     */
    protected function getDefaultAuthenticationRealm() {
        return "Service";
    }

    // End stuff that should get overridden.

    /**
     * Overrides OAuth2::__construct().
     */
    public function __construct() {
        parent::__construct();
    }

    /**
     * Release DB connection during destruct.
     */
    function __destruct() {
        
    }

    /**
     * Little helper function to add a new client to the database.
     *
     * Do NOT use this in production! This sample code stores the secret
     * in plaintext!
     *
     * @param $client_id
     *   Client identifier to be stored.
     * @param $client_secret
     *   Client secret to be stored.
     * @param $redirect_uri
     *   Redirect URI to be stored.
     */
    public function addClient($client_id, $client_secret, $redirect_uri) {

        $clientEnt = new ElggObject();
        $clientEnt->subtype = 'greengar_oauth2_client';
        $clientEnt->owner_guid = get_loggedin_userid();
        $clientEnt->container_guid = get_loggedin_userid();
        $clientEnt->client_id = $client_id;
        $clientEnt->client_secret = $client_secret;
        $clientEnt->redirect_uri = $redirect_uri;

        $clientEnt->access_id = ACCESS_PUBLIC;

        $clientEnt->save();

        return $clientEnt->getGUID();
    }

    /**
     * Implements OAuth2::checkClientCredentials().
     *
     * Do NOT use this in production! This sample code stores the secret
     * in plaintext!
     */
    protected function checkClientCredentials($client_id, $client_secret = NULL) {

        $pairs = array();

        $pairs[] = array(
            'name' => 'client_id',
            'value' => $client_id
        );

        $clientEnts =
                elgg_get_entities_from_metadata(
                        array(
                            'metadata_name_value_pairs' => $pairs,
                            'type' => 'object',
                            'subtype' => 'greengar_oauth2_client'
                        )
        );

        if ($clientEnts && $clientEnts[0]) {
            $clientEnt = $clientEnts[0];
            if ($clientEnt->client_secret) {
                return $client_secret == $clientEnt->client_secret;
            }
        }

        return false;
    }

    /**
     * Implements OAuth2::getRedirectUri().
     */
    protected function getRedirectUri($client_id) {
        $pairs = array();

        $pairs[] = array(
            'name' => 'client_id',
            'value' => $client_id
        );

        $clientEnts =
                elgg_get_entities_from_metadata(
                        array(
                            'metadata_name_value_pairs' => $pairs,
                            'type' => 'object',
                            'subtype' => 'greengar_oauth2_client'
                        )
        );

        if ($clientEnts && $clientEnts[0]) {
            $clientEnt = $clientEnts[0];
            return $clientEnt->redirect_uri;
        }

        return NULL;
    }

    /**
     * Implements OAuth2::getAccessToken().
     */
    protected function getAccessToken($oauth_token) {

        $pairs = array();

        $pairs[] = array(
            'name' => 'oauth_token',
            'value' => $oauth_token
        );

        $tokenEnts =
                elgg_get_entities_from_metadata(
                        array(
                            'metadata_name_value_pairs' => $pairs,
                            'type' => 'object',
                            'subtype' => 'greengar_oauth2_token'
                        )
        );

        if ($tokenEnts && $tokenEnts[0]) {
            $tokenEnt = $tokenEnts[0];
            $ces = array(
                'owner_guid' => $tokenEnt->owner_guid,
                'client_id' => $tokenEnt->client_id,
                'expires' => $tokenEnt->expires,
                'scope' => $tokenEnt->scope
            );
            return $ces;
        }

        return NULL;
    }

    /**
     * Implements OAuth2::setAccessToken().
     */
    protected function setAccessToken($oauth_token, $client_id, $expires, $scope = NULL) {

        $tokenEnt = new ElggObject();
        $tokenEnt->subtype = 'greengar_oauth2_token';

        $tokenEnt->owner_guid = get_loggedin_userid();
        $tokenEnt->container_guid = get_loggedin_userid();
        $tokenEnt->oauth_token = $oauth_token;
        $tokenEnt->client_id = $client_id;
        $tokenEnt->expires = $expires;
        $tokenEnt->scope = $scope;

        $tokenEnt->access_id = ACCESS_PUBLIC;

        $tokenEnt->save();

        return $tokenEnt->getGUID();
    }

    /**
     * Overrides OAuth2::getAuthCode().
     */
    protected function getAuthCode($code) {

        $pairs = array();

        $pairs[] = array(
            'name' => 'code',
            'value' => $code
        );

        $codeEnts =
                elgg_get_entities_from_metadata(
                        array(
                            'metadata_name_value_pairs' => $pairs,
                            'type' => 'object',
                            'subtype' => 'greengar_oauth2_code'
                        )
        );

        if ($codeEnts && $codeEnts[0]) {
            $codeEnt = $codeEnts[0];
            $ces = array(
                'owner_guid' => $codeEnt->owner_guid,
                'code' => $codeEnt->code,
                'client_id' => $codeEnt->client_id,
                'redirect_uri' => $codeEnt->redirect_uri,
                'expires' => $codeEnt->expires,
                'scope' => $codeEnt->scope
            );
            return $ces;
        }

        return NULL;
    }

    /**
     * Overrides OAuth2::setAuthCode().
     */
    protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $scope = NULL) {

        $codeEnt = new ElggObject();

        $codeEnt->subtype = 'greengar_oauth2_code';
        $codeEnt->owner_guid = get_loggedin_userid();
        $codeEnt->container_guid = get_loggedin_userid();
        $codeEnt->code = $code;
        $codeEnt->client_id = $client_id;
        $codeEnt->redirect_uri = $redirect_uri;
        $codeEnt->expires = $expires;
        $codeEnt->scope = $scope;

        $codeEnt->access_id = ACCESS_PUBLIC;

        $codeEnt->save();

        return $codeEnt->getGUID();
    }

    // Customize part
    public function directlyGrantToken($client_id, $scope = NULL) {
        //$client_id = get_input('client_id');
        $user = get_loggedin_user();
        $user->clearMetaData($client_id);
        $user->setMetadata($client_id, 1);
        $user->save();
        return $this->createAccessToken($client_id, $scope);
    }
}
